package com.bookocean.domain.service.impl;

import com.bookocean.application.common.RedisConstants;
import com.bookocean.application.exception.BusinessException;
import com.bookocean.application.exception.WeChatErrorCodeEnum;
import com.bookocean.controller.dto.NovelCompleteVoteDto;
import com.bookocean.domain.service.NovelCompleteVoteService;
import com.bookocean.domain.service.NovelDomainService;
import com.bookocean.domain.utils.RedisUtils;
import com.bookocean.domain.repository.NovelRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 小说完结投票领域服务实现类
 * 
 * @author BookOcean
 * @since 2024-01-01
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class NovelCompleteVoteServiceImpl implements NovelCompleteVoteService {
    
    private final RedisUtils redisUtils;
    private final NovelRepository novelRepository;
    private final NovelDomainService novelDomainService;
    
    @Override
    public NovelCompleteVoteDto initiateCompleteVote(Long novelId, Long userId) {
        log.info("发起完结投票，小说ID: {}, 发起者ID: {}", novelId, userId);
        
        // 检查是否已有投票进行中
        if (!canInitiateVote(novelId)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "已有完结投票进行中");
        }
        
        // 检查用户是否为参与者
        if (!novelRepository.isParticipant(novelId, userId)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "只有参与者可以发起完结投票");
        }
        
        // 获取参与者列表
        List<Long> participants = novelRepository.getNovelParticipantIds(novelId);
        if (CollectionUtils.isEmpty(participants)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "获取参与者列表失败");
        }
        
        // 计算需要的同意数量（超过半数）
        int totalParticipants = participants.size();
        int requiredAgrees = (totalParticipants / 2) + 1;
        
        // 生成投票ID
        String voteId = "vote_" + novelId + "_" + System.currentTimeMillis();
        
        // 创建投票信息
        NovelCompleteVoteDto voteDto = new NovelCompleteVoteDto();
        voteDto.setVoteId(voteId);
        voteDto.setNovelId(novelId);
        voteDto.setInitiatorId(userId);
        voteDto.setStatus("voting");
        voteDto.setCreateTime(LocalDateTime.now());
        voteDto.setExpireTime(LocalDateTime.now().plusHours(RedisConstants.COMPLETE_VOTE_EXPIRE_HOURS));
        voteDto.setTotalParticipants(totalParticipants);
        voteDto.setRequiredAgrees(requiredAgrees);
        voteDto.setAgreeCount(0);
        voteDto.setOpposeCount(0);
        
        // 存储到Redis
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        String recordsKey = RedisConstants.NOVEL_COMPLETE_VOTE_RECORDS_PREFIX + novelId;
        String detailsKey = RedisConstants.NOVEL_COMPLETE_VOTE_DETAILS_PREFIX + novelId;
        String participantsKey = RedisConstants.NOVEL_PARTICIPANTS_PREFIX + novelId;
        
        // 使用分布式锁确保原子性
        String lockKey = "lock:novel:vote:" + novelId;
        String lockValue = userId + "_" + System.currentTimeMillis();
        
        try {
            // 获取分布式锁
            if (!redisUtils.setNx(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
                throw new BusinessException(WeChatErrorCodeEnum.SYSTEM_ERROR, "系统繁忙，请稍后重试");
            }
            
            // 存储投票基本信息
            Map<String, Object> voteInfo = new HashMap<>();
            voteInfo.put("voteId", voteDto.getVoteId());
            voteInfo.put("novelId", voteDto.getNovelId());
            voteInfo.put("initiatorId", voteDto.getInitiatorId());
            voteInfo.put("status", voteDto.getStatus());
            voteInfo.put("createTime", voteDto.getCreateTime());
            voteInfo.put("expireTime", voteDto.getExpireTime());
            voteInfo.put("totalParticipants", voteDto.getTotalParticipants());
            voteInfo.put("requiredAgrees", voteDto.getRequiredAgrees());
            voteInfo.put("agreeCount", voteDto.getAgreeCount());
            voteInfo.put("opposeCount", voteDto.getOpposeCount());
            voteInfo.put("result", voteDto.getResult());
            
            redisUtils.hSetAll(voteKey, voteInfo);
            redisUtils.expire(voteKey, RedisConstants.COMPLETE_VOTE_EXPIRE_HOURS, TimeUnit.HOURS);
            
            // 存储参与者列表
            for (Long participantId : participants) {
                redisUtils.sAdd(participantsKey, participantId.toString());
            }
            redisUtils.expire(participantsKey, RedisConstants.COMPLETE_VOTE_EXPIRE_HOURS, TimeUnit.HOURS);
            
            // 设置投票记录和详情的过期时间
            redisUtils.expire(recordsKey, RedisConstants.COMPLETE_VOTE_EXPIRE_HOURS, TimeUnit.HOURS);
            redisUtils.expire(detailsKey, RedisConstants.COMPLETE_VOTE_EXPIRE_HOURS, TimeUnit.HOURS);
            
            log.info("完结投票发起成功，小说ID: {}, 投票ID: {}", novelId, voteId);
            
        } finally {
            // 释放分布式锁
            redisUtils.releaseLock(lockKey, lockValue);
        }
        
        return voteDto;
    }
    
    @Override
    public NovelCompleteVoteDto voteForComplete(Long novelId, Long userId) {
        return castVote(novelId, userId, "agree");
    }
    
    @Override
    public NovelCompleteVoteDto voteAgainstComplete(Long novelId, Long userId) {
        return castVote(novelId, userId, "oppose");
    }
    
    /**
     * 投票的通用方法
     */
    private NovelCompleteVoteDto castVote(Long novelId, Long userId, String voteType) {
        log.info("用户投票，小说ID: {}, 用户ID: {}, 投票类型: {}", novelId, userId, voteType);
        
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        String recordsKey = RedisConstants.NOVEL_COMPLETE_VOTE_RECORDS_PREFIX + novelId;
        String detailsKey = RedisConstants.NOVEL_COMPLETE_VOTE_DETAILS_PREFIX + novelId;
        String participantsKey = RedisConstants.NOVEL_PARTICIPANTS_PREFIX + novelId;
        
        // 检查投票是否存在
        if (!redisUtils.hasKey(voteKey)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "投票不存在或已过期");
        }
        
        // 检查用户是否为参与者
        if (!redisUtils.sIsMember(participantsKey, userId.toString())) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "只有参与者可以投票");
        }
        
        // 检查是否已投票
        if (redisUtils.sIsMember(recordsKey, userId.toString())) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "您已经投过票了");
        }
        
        // 检查投票状态
        String status = (String) redisUtils.hGet(voteKey, "status");
        if (!"voting".equals(status)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "投票已结束");
        }
        
        // 使用分布式锁确保投票原子性
        String lockKey = "lock:novel:vote:" + novelId;
        String lockValue = userId + "_" + System.currentTimeMillis();
        
        try {
            // 获取分布式锁
            if (!redisUtils.setNx(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
                throw new BusinessException(WeChatErrorCodeEnum.SYSTEM_ERROR, "系统繁忙，请稍后重试");
            }
            
            // 再次检查是否已投票（防止并发）
            if (redisUtils.sIsMember(recordsKey, userId.toString())) {
                throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "您已经投过票了");
            }
            
            // 记录投票
            redisUtils.sAdd(recordsKey, userId.toString());
            redisUtils.hSet(detailsKey, userId.toString(), voteType);
            
            // 更新投票统计
            String agreeCountStr = (String) redisUtils.hGet(voteKey, "agreeCount");
            String opposeCountStr = (String) redisUtils.hGet(voteKey, "opposeCount");
            
            int agreeCount = agreeCountStr != null ? Integer.parseInt(agreeCountStr) : 0;
            int opposeCount = opposeCountStr != null ? Integer.parseInt(opposeCountStr) : 0;
            
            if ("agree".equals(voteType)) {
                agreeCount++;
                redisUtils.hSet(voteKey, "agreeCount", agreeCount);
            } else {
                opposeCount++;
                redisUtils.hSet(voteKey, "opposeCount", opposeCount);
            }
            
            // 检查投票结果
            String requiredAgreesStr = (String) redisUtils.hGet(voteKey, "requiredAgrees");
            int requiredAgrees = requiredAgreesStr != null ? Integer.parseInt(requiredAgreesStr) : 0;
            
            String totalParticipantsStr = (String) redisUtils.hGet(voteKey, "totalParticipants");
            int totalParticipants = totalParticipantsStr != null ? Integer.parseInt(totalParticipantsStr) : 0;
            
            int totalVoted = agreeCount + opposeCount;
            int requiredOpposes = (totalParticipants / 2) + 1; // 超过半数反对
            
            // 判断投票结果
            if (agreeCount >= requiredAgrees) {
                // 同意数量达到要求，完结小说
                redisUtils.hSet(voteKey, "status", "completed");
                redisUtils.hSet(voteKey, "result", "agree");
                redisUtils.hSet(voteKey, "completedTime", LocalDateTime.now());
                
                // 调用完结小说方法
                try {
                    novelDomainService.completeNovel(novelId, userId);
                    log.info("投票通过，小说已完结，小说ID: {}", novelId);
                } catch (Exception e) {
                    log.error("完结小说失败，小说ID: {}, 错误: {}", novelId, e.getMessage(), e);
                    // 回滚投票状态
                    redisUtils.hSet(voteKey, "status", "voting");
                    redisUtils.hSet(voteKey, "result", null);
                    throw new BusinessException(WeChatErrorCodeEnum.SYSTEM_ERROR, "完结小说失败");
                }
                
            } else if (opposeCount >= requiredOpposes) {
                // 反对数量达到要求，投票结束，不完结
                redisUtils.hSet(voteKey, "status", "completed");
                redisUtils.hSet(voteKey, "result", "oppose");
                redisUtils.hSet(voteKey, "completedTime", LocalDateTime.now());
                log.info("投票结束，反对完结，小说ID: {}", novelId);
                
            } else if (totalVoted >= totalParticipants) {
                // 所有人都投票了，但同意和反对数量都不足半数
                redisUtils.hSet(voteKey, "status", "completed");
                redisUtils.hSet(voteKey, "result", "oppose");
                redisUtils.hSet(voteKey, "completedTime", LocalDateTime.now());
                log.info("所有人已投票，但同意数量不足，投票结束，小说ID: {}", novelId);
            }
            
        } finally {
            // 释放分布式锁
            redisUtils.releaseLock(lockKey, lockValue);
        }
        
        // 返回更新后的投票信息
        return getCompleteVoteStatus(novelId, userId);
    }
    
    @Override
    public boolean cancelCompleteVote(Long novelId, Long userId) {
        log.info("取消完结投票，小说ID: {}, 用户ID: {}", novelId, userId);
        
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        
        // 检查投票是否存在
        if (!redisUtils.hasKey(voteKey)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "投票不存在");
        }
        
        // 检查是否为发起者
        String initiatorIdStr = (String) redisUtils.hGet(voteKey, "initiatorId");
        if (initiatorIdStr == null || !initiatorIdStr.equals(userId.toString())) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "只有发起者可以取消投票");
        }
        
        // 检查投票状态
        String status = (String) redisUtils.hGet(voteKey, "status");
        if (!"voting".equals(status)) {
            throw new BusinessException(WeChatErrorCodeEnum.PARAM_ERROR, "投票已结束，无法取消");
        }
        
        // 使用分布式锁
        String lockKey = "lock:novel:vote:" + novelId;
        String lockValue = userId + "_" + System.currentTimeMillis();
        
        try {
            if (!redisUtils.setNx(lockKey, lockValue, 10, TimeUnit.SECONDS)) {
                throw new BusinessException(WeChatErrorCodeEnum.SYSTEM_ERROR, "系统繁忙，请稍后重试");
            }
            
            // 删除投票相关数据
            String recordsKey = RedisConstants.NOVEL_COMPLETE_VOTE_RECORDS_PREFIX + novelId;
            String detailsKey = RedisConstants.NOVEL_COMPLETE_VOTE_DETAILS_PREFIX + novelId;
            String participantsKey = RedisConstants.NOVEL_PARTICIPANTS_PREFIX + novelId;
            
            redisUtils.delete(voteKey);
            redisUtils.delete(recordsKey);
            redisUtils.delete(detailsKey);
            redisUtils.delete(participantsKey);
            
            log.info("完结投票取消成功，小说ID: {}", novelId);
            return true;
            
        } finally {
            redisUtils.releaseLock(lockKey, lockValue);
        }
    }
    
    @Override
    public NovelCompleteVoteDto getCompleteVoteStatus(Long novelId, Long userId) {
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        
        // 检查投票是否存在
        if (!redisUtils.hasKey(voteKey)) {
            return null;
        }
        
        // 检查投票是否过期
        checkAndCleanExpiredVote(novelId);
        
        // 获取投票基本信息
        Map<Object, Object> voteInfo = redisUtils.hGetAll(voteKey);
        if (CollectionUtils.isEmpty(voteInfo)) {
            return null;
        }
        
        NovelCompleteVoteDto voteDto = new NovelCompleteVoteDto();
        voteDto.setVoteId((String) voteInfo.get("voteId"));
        voteDto.setNovelId(novelId);
        voteDto.setInitiatorId(Long.parseLong((String) voteInfo.get("initiatorId")));
        voteDto.setStatus((String) voteInfo.get("status"));
        voteDto.setCreateTime((LocalDateTime) voteInfo.get("createTime"));
        voteDto.setExpireTime((LocalDateTime) voteInfo.get("expireTime"));
        voteDto.setTotalParticipants((Integer) voteInfo.get("totalParticipants"));
        voteDto.setRequiredAgrees((Integer) voteInfo.get("requiredAgrees"));
        voteDto.setAgreeCount((Integer) voteInfo.get("agreeCount"));
        voteDto.setOpposeCount((Integer) voteInfo.get("opposeCount"));
        voteDto.setResult((String) voteInfo.get("result"));
        
        // 如果用户已登录，检查投票状态
        if (userId != null) {
            String recordsKey = RedisConstants.NOVEL_COMPLETE_VOTE_RECORDS_PREFIX + novelId;
            String detailsKey = RedisConstants.NOVEL_COMPLETE_VOTE_DETAILS_PREFIX + novelId;
            
            boolean hasVoted = redisUtils.sIsMember(recordsKey, userId.toString());
            voteDto.setHasVoted(hasVoted);
            
            if (hasVoted) {
                String userVote = (String) redisUtils.hGet(detailsKey, userId.toString());
                voteDto.setUserVote(userVote);
            }
        }
        
        return voteDto;
    }
    
    @Override
    public boolean canInitiateVote(Long novelId) {
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        
        // 检查是否已有投票
        if (!redisUtils.hasKey(voteKey)) {
            return true;
        }
        
        // 检查投票是否过期
        checkAndCleanExpiredVote(novelId);
        
        // 检查投票状态
        String status = (String) redisUtils.hGet(voteKey, "status");
        return !"voting".equals(status);
    }
    
    /**
     * 检查并清理过期的投票
     */
    private void checkAndCleanExpiredVote(Long novelId) {
        String voteKey = RedisConstants.NOVEL_COMPLETE_VOTE_PREFIX + novelId;
        
        if (!redisUtils.hasKey(voteKey)) {
            return;
        }
        
        String status = (String) redisUtils.hGet(voteKey, "status");
        if (!"voting".equals(status)) {
            return;
        }
        
        // 检查是否过期
        LocalDateTime expireTime = (LocalDateTime) redisUtils.hGet(voteKey, "expireTime");
        if (expireTime != null && LocalDateTime.now().isAfter(expireTime)) {
            // 投票过期，设置为过期状态
            redisUtils.hSet(voteKey, "status", "expired");
            redisUtils.hSet(voteKey, "result", "expired");
            redisUtils.hSet(voteKey, "completedTime", LocalDateTime.now());
            
            log.info("投票过期，小说ID: {}", novelId);
        }
    }
}
